#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "fblib.h"

struct fb_dev* fb_open(char* const devname, int  mode)
{
	struct fb_dev* dev = malloc(sizeof(struct fb_dev));
	if(NULL == dev) {
		perror("fb_open::malloc fb_dev");
		return NULL;
	}
	int fd = open(devname, mode);
	if(-1 == fd) {
		perror("fb_open::open");
		free(dev);
		return NULL;
	} 

	dev->fd = fd;

	int retv;
	retv = ioctl(fd, FBIOGET_VSCREENINFO, &(dev->fb_var_screeninfo));
	if(retv != 0) {
		perror("fb_open::ioctl");
		free(dev);
		return NULL;
	}

	retv = ioctl(fd, FBIOGET_FSCREENINFO, &(dev->fb_fix_screeninfo));
	if(retv != 0) {
		perror("fb_open::ioctl");
		free(dev);
		return NULL;
	}

	dev->bits_per_pixel = dev->fb_var_screeninfo.bits_per_pixel;
	dev->xres = dev->fb_var_screeninfo.xres;
	dev->yres = dev->fb_var_screeninfo.yres;
	dev->buffer_size = dev->xres * dev->yres * dev->bits_per_pixel / 8;
	switch(dev->bits_per_pixel) {
		case 8:
			dev->mask = 0xFF;
		case 16:
			dev->mask = 0xFFFF;
			break;
		case 24:
			dev->mask = 0xFFFFFF;
			break;
		case 32:
			dev->mask = 0xFFFFFFFF;
			break;
		default:
			dev->mask = 0xFF;
			break;
	}
	dev->double_buffer = malloc(dev->buffer_size);
	if(dev->double_buffer == NULL) {
		perror("fb_open::malloc");
		free(dev);
	}
	memset(dev->double_buffer, 0, dev->buffer_size);

	dev->start_buffer = (char*)mmap(0, dev->buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, dev->fd, 0);


	if((void*)(dev->start_buffer) == MAP_FAILED) {
		perror("fb_open::mmap");
		free(dev);
		return NULL;
	}
	return dev;
}

void fb_close(struct fb_dev* dev)
{
	munmap(dev->start_buffer, dev->buffer_size);
	close(dev->fd);
	free(dev->double_buffer);
	free(dev);
}

void fb_info(struct fb_dev* const dev)
{
	printf("fd           : %d\n", dev->fd);
	printf("xres         : %d\n", dev->xres);
	printf("yres         : %d\n", dev->yres);
	printf("color mask   : %x\n", dev->mask);
	printf("bits_per_pixel: %d\n", dev->fb_var_screeninfo.bits_per_pixel);
	printf("buffer_size  : %d\n", dev->buffer_size);
	printf("start_buffer : %p\n", dev->start_buffer);
	printf("double_buffer: %p\n", dev->double_buffer);
}
long location(struct fb_dev* const dev, int x, int y) 
{
	if(x < 0 || y < 0)
		return 0;
	long temp;
	temp = (x+dev->fb_var_screeninfo.xoffset) * dev->bits_per_pixel/8 +
		(y+dev->fb_var_screeninfo.yoffset) * dev->fb_fix_screeninfo.line_length;
	if(temp > dev->buffer_size)
		return 0;
	return temp;
}

void fb_draw_pixel(struct fb_dev* const dev, int x, int y, int color)
{
	long addr = location(dev, x, y);
	//   memset(dev->buffer_star+addr, color & dev->mask, dev->bits_per_pixel/8);
	*((int*)(dev->double_buffer + addr)) &= ~(dev->mask);
	*((int*)(dev->double_buffer + addr)) |= (color & dev->mask);
}
void fb_fill_rect(struct fb_dev* const dev, int x, int y, int width, int height, int color)
{
	int yy;
	int xx;
	for(yy = 0; yy < height; ++yy) {
		for(xx = 0; xx < width; ++xx) {
			fb_draw_pixel(dev, x+xx, y+yy, color);
		}
	}
}
void fb_draw_line(struct fb_dev* const dev, int x1, int y1, int x2, int y2, int color)
{
	int y;
	int x;
	if(x1 == x2) { // draw a vertical line
		if(y2 >= y1) {
			for(y = y1; y <= y2; ++y) {
				fb_draw_pixel(dev, x1, y, color);
			}
		} else {
			for(y = y2; y <= y1; ++y) {
				fb_draw_pixel(dev, x1, y, color);
			}
		}
		return;
	}

	if(x1 < x2) {
		for(x = x1; x <= x2; ++x) {
			y = y1 + (y2-y1) * x / (x2-x1);
			fb_draw_pixel(dev, x, y, color);
		}
	} else { // x2 < x1
		for(x = x2; x <= x1; ++x) {
			y = y2 + (y1-y2) * x / (x1-x2);
			fb_draw_pixel(dev, x, y, color);
		}
	}
}

void fb_draw_rect(struct fb_dev* const dev, int x, int y, int width, int height, int color)
{
	fb_draw_line(dev, x, y, x, y+height, color); // draw left |
	fb_draw_line(dev, x, y, x+width, y, color);  // draw up -
	fb_draw_line(dev, x+width, y, x+width, y+height, color); // draw right |
	fb_draw_line(dev, x, y+height, x+width, y+height, color); // draw down -
}
void fb_draw_circle(struct fb_dev* const dev, int x, int y, int radius, int color)
{
	// radius^2 = (x - xx)^2 + hei^2
	// hei = (radius^2 - (x - xx)^2)^0.5
	int xx;
	int hei;
	for(xx = x - radius; xx <= x + radius; ++xx) {
		hei = sqrt(radius*radius - (x - xx)*(x - xx));
		fb_draw_pixel(dev, xx, y + hei, color);
		fb_draw_pixel(dev, xx, y - hei, color);
	}
}
void fb_fill_circle(struct fb_dev* const dev, int x, int y, int radius, int color)
{
	int xx;
	int hei;
	for(xx = x - radius; xx <= x + radius; xx++) {
		hei = sqrt(radius*radius - (x - xx)*(x - xx));
		fb_draw_line(dev, xx, y-hei, xx, y+hei, color);
	}
}

void fb_draw_bitmap(struct fb_dev* const dev, Rect des_rect, Rect res_rect, 
		const Bitmap* bitmap, int draw_way)
{
	int y;
	int x;
	int xx;
	int yy;
	int res_x;
	int res_y;
	float scale_x = (float)res_rect.width / (float)des_rect.width;
	float scale_y = (float)res_rect.height / (float)des_rect.height;
	unsigned int color;
	for(y = 0; y < des_rect.height; ++y) {
		for(x = 0; x < des_rect.width; ++x) {
			res_x = x * scale_x + res_rect.x;
			res_y = y * scale_y + res_rect.y;
			/*
			if(bitmap->bits_per_pixel == 24) {
				char* begin = (char*)(bitmap->data) + bitmap->extra*res_y +
					(res_y*bitmap->xres + res_x)*bitmap->bits_per_pixel/8;
				unsigned char blue = *(begin +0);
				unsigned char green = *(begin+1);
				unsigned char red = *(begin +2);
				color = RGB24TO16(red, green, blue);
			}
			*/
			char* begin = (char*)(bitmap->data) + bitmap->extra*res_y +
				(res_y*bitmap->xres + res_x)*bitmap->bits_per_pixel/8;
			/*
			char* begin = (char*)(bitmap->data) + bitmap->extra*bitmap->bits_per_pixel/8*res_y +
				(res_y*bitmap->xres + res_x)*bitmap->bits_per_pixel/8;
				*/

			fb_convert_color(&color, dev->bits_per_pixel, begin, bitmap->bits_per_pixel);
			if(draw_way & DrawInvertY) {
				yy = des_rect.height - y + des_rect.y;
			} else {
				yy = y + des_rect.y;
			}
			if(draw_way & DrawInvertX) {
				xx = des_rect.width - x + des_rect.x;
			} else {
				xx = x + des_rect.x;
			}
			if(draw_way & DrawAnd) {

			}
			if(draw_way & DrawOr) {

			}
			fb_draw_pixel(dev, xx, yy, color);
		}
	}
}

/*  
	void fb_draw_bitmap(struct fb_dev* const dev, int x, int y, int bitmap)
	{
	BitmapHeader header;

	lseek(bitmap, 0L, SEEK_SET);
	if(sizeof(BitmapHeader) != read(bitmap, &header, sizeof(BitmapHeader))) {
	fprintf(stderr, "can't read bitmap header\n");
	return;
	}

	int data_size = header.info_header.biImageSize;
	char* data_buffer = malloc(data_size);
	if(NULL == data_buffer) {
	printf("can't alloc bitmap data buffer\n");
	return;
	}

	if(data_size != read(bitmap, data_buffer, data_size)) {
	printf("Can't read bitmap data into buffer\n");
	return;
	}

	int xres = header.info_header.biWidth;
	while(xres % 4 != 0)
	xres++;
	int yres = header.info_header.biHeight;

	int yy;
	int xx;

	Rgb rgb;
	for(yy = 0; yy < yres; ++yy) {
	for(xx = 0; xx < xres; ++xx) {
	rgb = *(Rgb*)(data_buffer+(yy*xres+xx)*24/8);
	unsigned short color = RGB24TO16(rgb.red, rgb.green, rgb.blue);
	if(color == 0xFFFF)
	continue;
	fb_draw_pixel(dev, x+xx, y+yres-yy, 
	color);

	}
	}

	free(data_buffer);
	}
	*/

void fb_repaint(struct fb_dev* const dev, int x, int y, int width, int height)
{
	if(x + width > dev->xres)
		width = dev->xres-x;
	if(y + height > dev->yres)
		height = dev->yres-y;
	int yy;
	long addr;
	for(yy = 0; yy <= height; ++yy) {
		addr = location(dev, x, y+yy);
		memcpy(dev->start_buffer+addr, dev->double_buffer+addr, width*dev->bits_per_pixel/8);
	}
}
void fb_repaint_all(struct fb_dev* const dev)
{
	long addr = location(dev, 0, 0);
	memcpy(dev->start_buffer+addr, dev->double_buffer, dev->buffer_size);
}

// convert color into another form
// like 16 bits to 24 bits...
// *to save converted data, from is the origin color data, 
void fb_convert_color(char* to, int to_size, char* from, int from_size)
{
	if(from_size == 16) {
		unsigned short int data;
		data = *(unsigned short int*)from;
		switch(to_size) {
			case 16:
				*(unsigned short int *)to = *(unsigned short int *)from;
				break;
			case 24:
				*(to+2) = ((data>>11) & 0x1F)<<3;
				*(to+1) = ((data>>5)  & 0x3F)<<2;
				*(to+0) = ((data>>0)  & 0x1F)<<3;
				break;
			case 32:
				*(to+3) = 0x0;
				*(to+2) = ((data>>11) & 0x1F)<<3;
				*(to+1) = ((data>>5)  & 0x3F)<<2;
				*(to+0) = ((data>>0)  & 0x1F)<<3;
				break;
		}
	} else if(from_size == 24) {
		switch(to_size) {
			case 16:
				*(unsigned short int*)to = (*(from+2) & (0x1F<<3)) << 8 | (*(from+1) & (0x3F<<2)) << 3 |
					(*(from+0) & (0x1F<<3)) >> 3;
				break;
			case 24:
				*(to+2)= *(from+2);
				*(to+1)= *(from+1);
				*(to+0)= *(from+0);
				break;
			case 32:
				*(to+3) = 0x0;
				*(to+2)= *(from+2);
				*(to+1)= *(from+1);
				*(to+0)= *(from+0);
				break;
		}
	} else if(from_size == 32) {
		switch(to_size) {
			case 16:
				*(unsigned short int*)to = (*(from+2) & (0x1F<<3)) << 8 | (*(from+1) & (0x3F<<2)) << 3 |
					(*(from+0) & (0x1F<<3))>>3;
				break;
			case 24:
				*(to+2)= *(from+2);
				*(to+1)= *(from+1);
				*(to+0)= *(from+0);
				break;
			case 32:
				*(unsigned int *)to = *(unsigned int *)from;
				break;
		}
	} 
}
